package org.bhf.service.view;

/**
 * Copyright (c) 2007, Blue Hole Software. All rights reserved.
 * Code licensed under the Apache 2.0 License:
 * http://www.apache.org/licenses/
 */

import com.google.inject.Inject;
import org.bhf.providers.ccache.ComponentStateCache;
import org.bhf.service.Component;
import org.bhf.service.ComponentInfo;
import org.bhf.service.Constants;
import org.bhf.service.EventQueue;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Render the return result of a <code>Component</code> method invocation, which includes the result of the
 * method invocation and the new <code>Component</code> state. The result includes four properties:
 * <dl>
 * <dt>result</dt><dd>Return result, possibly null, of the invocation.</dd>
 * <dt>state</dt><dd>State to be overlaid on an older version of the <code>Component</code></dd>
 * <dt>version</dt><dd>Current version of the <code>Component</code></dd>
 * <dt>events</dt><dd>Current version of the <code>Component</code></dd>
 * </dl>
 * <p>If the reponse has already been committed, such as setting a redirect before processing the view, this
 * <code>ProcessView</code>, will do nothing as the reponse has effectively already been handled.</p>
 */
public class ComponentProcessView extends JSONProcessView
{
    private static final Object NO_STATE = new Object();
    @Inject
    private ComponentStateCache componentCache;

    private Component   component;
    private View        view;

    /**
     * Render the return result of a <code>Component</code> method invocation, which includes the result of the
     * method invocation and the new <code>Component</code> state.
     *
     * @param service The final form of this <code>Component</code>. Must not be <code>null</code>.
     * @param method The target service method
     * @param result May be <code>null</code> to indicate no return result (essentially void).
     */
    public ComponentProcessView( final Object service, final Method method, final Object result  )
    {
        super( service, method, result );

        if( !(service instanceof Component) )
            throw new RuntimeException( "Attempt to use ComponentProcessView with a service that is not a component: " + service.getClass().getName() );

        component = (Component)service;
        component.setVersion(); // Checks dirty bit and bumps version if dirty
        view = newView( component.isChanged() ? component : null, result );
        setResult( view );
    }

    /**
     * ProcessView contract. Override to save component state in the component cache
     *
     * @param request Servlet request.
     * @param response Serlvet reponse.
     * @param servletContext The servlet context for this application
     * @throws java.io.IOException ConversionError during JSON rendering
     * @exclude
     */
    @Override
    public void processView( final HttpServletRequest request, final HttpServletResponse response, final ServletContext servletContext )
        throws IOException
    {
        if( component.isChanged() && ComponentInfo.Util.isCachedServerSide( component.getClass() ) )
            componentCache.rememberComponent(
                component.getClass().getName(),
                component.getId(),
                component.getVersion(),
                component
            );

        view.setEvents( EventQueue.getEvents() );

        super.processView( request, response, servletContext );
    }

    //
    // Subclass Contract
    //

    protected   View    newView( final Component state, final Object result )
    {
        return new View( state, result );
    }

    //
    // Inner Classes
    //

    /**
     * @exclude
     */
    public static class View
    {
        private final Object                            state;
        private final Object                            result;
        private       List<EventQueue.Event>            events      = new ArrayList<EventQueue.Event>(0);

        public View( final Component state, final Object result )
        {
            this.state = state != null ? state : ComponentProcessView.NO_STATE;

            if( result == null )
                this.result = "null";
            else if( result == Constants.VOID )
                this.result = "undefined";
            else
                this.result = result;
        }

        public Object                   getState()
        {
            return state;
        }

        public Object                   getResult()
        {
            return result;
        }

        public List<EventQueue.Event>   getEvents()
        {
            return events;
        }

        public void                     setEvents( final List<EventQueue.Event> events )
        {
            this.events = events;
        }
    }
}
